﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Buddy.Common;
using Buddy.Common.Plugins;
using Buddy.Common.Xml;
using Buddy.CommonBot;
using Buddy.Swtor;
using Buddy.Swtor.Enums;
using Buddy.Swtor.Objects;
using Buddy.XmlEngine;
using Buddy.Swtor.Objects.Containers;
using System.Threading;
using System.Timers;
using System.Windows;
using System.IO;
using System.Reflection;
using System.Windows.Controls;

using EquipMe.Settings;
using System.Windows.Markup;

namespace EquipMe
{
    public class EquipMe : IPlugin
    {
        #region IPlugin interface
        public string Author
        {
            get { return "BobDole"; }
        }

        public string Description
        {
            get { return "Automatically equip gear based on stat weights."; }
        }

        #region ConfigWindow
        public Window _window;

        public System.Windows.Window DisplayWindow
        {
            get
            {
                try
                {
                    if (_window == null)
                        _window = new Window();

                    string assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
                    string assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
                    string xamlPath = Path.Combine(assemblyPath, "Plugins", "EquipMe", "Settings", "UI.xaml");

                    StreamReader xamlStream = new StreamReader(xamlPath);
                    DependencyObject xamlContent = System.Windows.Markup.XamlReader.Load(xamlStream.BaseStream) as DependencyObject;

                    // Change reference to custom class
                    

                    // This hooks up our object with our UserControl DataBinding
                    _window.DataContext = EquipMeSettings.Instance;

                    UserControl mainControl = LogicalTreeHelper.FindLogicalNode(xamlContent, "mainControl") as UserControl;
                    _window.Content = mainControl;
                    _window.Width = 500;
                    _window.Height = 400;
                    _window.MinWidth = 450;
                    _window.MinHeight = 400;
                    _window.ResizeMode = ResizeMode.CanResizeWithGrip;

                    _window.Title = "EquipMe";

                    _window.Closed += ConfigWindow_Closed;
                    Application.Current.Exit += ConfigWindow_Closed;

                    return _window;
                }
                catch (Exception ex)
                {
                    Log("Error opening Config window: " + ex);
                    return null;
                }
            }
        }

        void ConfigWindow_Closed(object sender, EventArgs e)
        {
            if (_window != null)
            {
                //Set variables and stat weights
                Log("Settings Changed");
                setToggles();
                buildStatWeights(Me.CharacterClass, Me.AdvancedClass, Me.Discipline);

                _window.Closed -= ConfigWindow_Closed;
                _window = null;
            }
        }
        #endregion

        public string Name
        {
            get { return "EquipMe"; }
        }

        public void OnDisabled()
        {
            GameEvents.InventorySlotsChanged -= inventoryChange;
        }

        public void OnEnabled()
        {
            Log("Enabled");
            timeOutTimer = new System.Timers.Timer(30000);
            timeOutTimer.Elapsed += timeOutTimer_Elapsed;

            GameEvents.InventorySlotsChanged += inventoryChange;
        }

        public void OnInitialize()
        {
            Log("v" + Version + " Initialized");
            slotList = new List<SlotId>();
            StatWeights = new Dictionary<ModStat, float>();
            ignoreList = new List<ulong>();
            //Set auto-equip, lockboxes, rep items
            setToggles();
            buildEquippableSlots(Me.CharacterClass, Me.AdvancedClass, Me.Discipline);
            buildStatWeights(Me.CharacterClass, Me.AdvancedClass, Me.Discipline);
        }

        public void OnPulse()
        {
            
        }

        public void OnShutdown()
        {
            
        }

        public Version Version
        {
            get { return new Version(0, 14); }
        }

        public bool Equals(IPlugin other)
        {
            return other.Name == Name;
        }

        public static void Log(string message)
        {
            Logging.Write("[EquipMe] " + message);
        }
        #endregion

        protected static Buddy.Swtor.Objects.TorPlayer Me { get { return BuddyTor.Me; } }

        private Dictionary<ModStat, float> StatWeights;
        private List<SlotId> slotList;
        //Toggles
        private bool autoEquip;
        private bool openLockboxes;
        private bool useRepTrophies;
        //Set true to try checking Prototype stats (NOt working as of yet)
        private const bool incEnhancements = true;
        //Set to false if you want to equip over prototype(orange) gear
        private const bool skipOrange = true;
        //Will change based on advanced class
        private bool doubleWeaps = false;
        private int oldInvCount = 0;
        private int currInvCount;
        List<ulong> ignoreList;
        string[] lockboxes = new string[] { "Credit Box", "Credit Case", "Storage Box", "Storage Case", "Security Box", "Security Case", "Mission Box", "Mission Case" };

        string[] repTrophies = new string[] { "CZ-198 Lab Records", "Czerka Weapons Schematics", "\"Vigilant\" System Datafiles", "BBA Official Mark of Service", 
            "BBA Official Certificate of Accomplishment", "Rare Munitions", "Underworld Intel", "Travel Records", "Makeb HoloSouvenir", "Repair Toolkit", "Energy Field Monitor", 
            "GSI Share Certificate", "Gree Memory Chip", "Gree Data Core", "Ancient Gree Artifact", "Voss Mystic's Orb", "Massassi Pendant", "Mark of Cooperation", 
            "Revanite Dagger", "Exonium Ore", "Raider's Cove Saber" };
        private System.Timers.Timer timeOutTimer;
        private bool timeOut;


        private void inventoryChange()
        {
            if (Me.InCombat) return;

            //If inventory is getting smaller, don't check for new equips
            currInvCount = Me.InventoryEquipment.Count();
            Log("Beginning inv count: " + currInvCount + " vs old count: " + oldInvCount);
            if (oldInvCount >= currInvCount)
            {
                oldInvCount = currInvCount;
                return;
            }

            //Loop through each item in inventory
            foreach (TorItem item in Me.InventoryEquipment)
            {

                //Check if item is a lockbox and open it
                if (item.IsGiftBox && openLockboxes && lockboxes.Any(s => item.Name.Contains(s)))
                {
                    Log("Opening " + item.Name);
                    item.Use();
                    continue;
                }
                //Check if item is reputation trophy and use it
                if (item.IsCurrency && useRepTrophies && repTrophies.Any(s => item.Name.Contains(s)))
                {
                    int stackCount = Convert.ToInt32(item.StackCount);
                    timeOut = false;
                    timeOutTimer.Start();
                    Log("Using rep item: " + item.Name + " - Stack: " + item.StackCount);
                    while (item.StackCount > 0)
                    {
                        item.Use();
                        Thread.Sleep(4000);
                        if (timeOut)
                        {
                            Log("Timeout while trying to use " + item.Name);
                            break;
                        }
                    }
                    timeOutTimer.Stop();
                    continue;
                }

                if (!item.IsEquipable || item.IsEnhancement) continue;

                //If class can wear item
                if (autoEquip && Me.CanEquipItem(item) == ContainerResult.Ok)
                {
                    if (ignoreList.Contains(item.Guid))
                        continue;

                    HashSet<SlotId> slotTypes = new HashSet<SlotId>();
                    for (int i = 0; i < 10; i++)
                    {
                        slotTypes = item.SlotTypes;
                        if (slotTypes.Any())
                            break;
                        Thread.Sleep(100);
                    }
                    SlotId slot = slotTypes.Intersect(slotList).FirstOrDefault();
                    Log("Checking: " + item.Name + " -- " + string.Join(", ", slotTypes) + ". Matching slot: " + slot);
                    if (slot != 0)
                    {
                        List<TorItem> equippedList = Me.InventoryEquipped.Where(c => c.SlotTypes.Contains(slot)).ToList();

                        if (doubleWeaps)
                        {
                            //Add off-hand slot if double weapon user and weapon has primary slotType (contains both primary and off-hand)
                            if (slot == SlotId.EquipHumanMainHand && slotTypes.Contains(SlotId.EquipHumanOffHand))
                            {
                                TorItem temp = Me.InventoryEquipped.Where(c => c.SlotTypes.Contains(SlotId.EquipHumanOffHand)).FirstOrDefault();
                                equippedList.Add(temp);
                            }
                            else if (slot == SlotId.EquipHumanRangedPrimary && slotTypes.Contains(SlotId.EquipHumanRangedSecondary))
                            {
                                TorItem temp = Me.InventoryEquipped.Where(c => c.SlotTypes.Contains(SlotId.EquipHumanRangedSecondary)).FirstOrDefault();
                                equippedList.Add(temp);
                            }
                            else if (slot == SlotId.EquipHumanOffHand || slot == SlotId.EquipHumanRangedSecondary)
                            {
                                Log(item.Name + " is offhand only. " + string.Join(", ", equippedList.ConvertAll(a => a.Name)));
                                if (equippedList.Count >= 2)
                                    equippedList.RemoveAt(0);
                            }
                        }
                        

                        //Count how many occurences of the slot exists (ex. count would be 2 if two implants are equipped)
                        int count = equippedList.Count;
                        if (count > 0)
                        {
                            //Equip item automatically if double-slot and only one item equipped
                            if (count < 2 && (slot == SlotId.EquipHumanRelic || slot == SlotId.EquipHumanImplant))
                            {
                                Log("Equipping " + item.Name + " because at least one slot is empty.");
                                Me.EquipItem(item);
                            }
                            //Check if double-weapon user and only have 1 weapon equipped
                            else if (count < 2 && doubleWeaps && slot != SlotId.EquipHumanOffHand && slot != SlotId.EquipHumanRangedSecondary && (item.SlotTypes.Contains(SlotId.EquipHumanOffHand) || item.SlotTypes.Contains(SlotId.EquipHumanRangedSecondary)))
                            {
                                Log("Equipping " + item.Name + " because at least one weapon slot is empty.");
                                Me.EquipItem(item);
                            }
                            else
                            {
                                //Check each matching slot and compare weights
                                foreach (TorItem equippedItem in equippedList)
                                {
                                    Log(equippedItem.Name + " " + equippedItem.Quality + " -- " + equippedItem.GetItemStats().Count);
                                    if (skipOrange && equippedItem.Quality == ItemQuality.Prototype && equippedItem.GetItemStats().Count <= 3 && slot != SlotId.EquipHumanRelic)
                                    {
                                        Log("Skipping " + equippedItem.Name + " because it is Custom(Orange).");
                                        continue;
                                    }
                                    float weightInv = calculateWeight(item);
                                    float weightEquipped = calculateWeight(equippedItem);
                                    Log("Comparing " + item.Name + "(" + weightInv + ") to " + equippedItem.Name + "(" + weightEquipped + ")");
                                    if (weightInv > weightEquipped)
                                    {
                                        //Use item to remove it UNTESTED
                                        Log("Equipping " + item.Name);
                                        equippedItem.Use();
                                        Me.EquipItem(item);
                                        break;
                                    }
                                }
                            }
                        }
                        else
                        {
                            //Equip item because slot is empty
                            Log("Equipping " + item.Name + " because slot is empty.");
                            Me.EquipItem(item);
                        }
                    }
                    else
                    {
                        Log("Could not get slot type of " + item.Name);
                    }
                }
                ignoreList.Add(item.Guid);
            }
            Me.TakeLoot();
            Log("End of checking. Total inv count: " + currInvCount);
            oldInvCount = currInvCount;
        }

        void timeOutTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            timeOut = true;
            timeOutTimer.Stop();
        }

        void setToggles()
        {
            autoEquip = EquipMeSettings.Instance.EnableAutoEquip;
            openLockboxes = EquipMeSettings.Instance.EnableLockboxes;
            useRepTrophies = EquipMeSettings.Instance.EnableRepItems;

            Log("Auto-equip items: " + autoEquip);
            Log("Open lockboxes: " + openLockboxes);
            Log("Use reputation trophies: " + useRepTrophies);
        }

        #region StatWeights
        private void buildStatWeights(CharacterClass basic, AdvancedClass advanced, CharacterDiscipline discipline)
        {
            if (autoEquip)
            {
                Log("Building stat weights");
                StatWeights = new Dictionary<ModStat, float>();
                switch (basic)
                {
                    case CharacterClass.BountyHunter:
                    case CharacterClass.Trooper:
                        StatWeights.Add(ModStat.Aim, EquipMeSettings.Instance.StatMain);
                        StatWeights.Add(ModStat.TechPowerRating, EquipMeSettings.Instance.StatTechForce);
                        break;
                    case CharacterClass.Agent:
                    case CharacterClass.Smuggler:
                        StatWeights.Add(ModStat.Cunning, EquipMeSettings.Instance.StatMain);
                        StatWeights.Add(ModStat.TechPowerRating, EquipMeSettings.Instance.StatTechForce);
                        break;
                    case CharacterClass.Inquisitor:
                    case CharacterClass.Consular:
                        StatWeights.Add(ModStat.Willpower, EquipMeSettings.Instance.StatMain);
                        StatWeights.Add(ModStat.ForcePowerRating, EquipMeSettings.Instance.StatTechForce);
                        break;
                    case CharacterClass.Warrior:
                    case CharacterClass.Knight:
                        StatWeights.Add(ModStat.Strength, EquipMeSettings.Instance.StatMain);
                        StatWeights.Add(ModStat.ForcePowerRating, EquipMeSettings.Instance.StatTechForce);
                        break;
                }

                StatWeights.Add(ModStat.Endurance, EquipMeSettings.Instance.StatEndurance);
                StatWeights.Add(ModStat.DamageMin, EquipMeSettings.Instance.StatDamage);
                StatWeights.Add(ModStat.ArmorRating, EquipMeSettings.Instance.StatArmor);
                StatWeights.Add(ModStat.CriticalChanceRating, EquipMeSettings.Instance.StatCritChance);
                StatWeights.Add(ModStat.CriticalDamageRating, EquipMeSettings.Instance.StatSurge); //Surge
                StatWeights.Add(ModStat.AttackPowerRating, EquipMeSettings.Instance.StatPower); //Power
                StatWeights.Add(ModStat.Accuracy, EquipMeSettings.Instance.StatAccuracy);
                StatWeights.Add(ModStat.DefenseRating, EquipMeSettings.Instance.StatDefense);
                StatWeights.Add(ModStat.GlanceAbsorbRating, EquipMeSettings.Instance.StatGlance);
                StatWeights.Add(ModStat.AlacrityRating, EquipMeSettings.Instance.StatAlacrity);
                StatWeights.Add(ModStat.PvpExpertiseRating, EquipMeSettings.Instance.StatExpertise);

                foreach (KeyValuePair<ModStat, float> w in StatWeights)
                {
                    if (w.Value != 0)
                        Log(w.Key + " " + w.Value);
                }
            }
        }
        #endregion

        #region Class Equipables
        private void buildEquippableSlots(CharacterClass basic, AdvancedClass advanced, CharacterDiscipline discipline)
        {
            switch (basic)
            {
                case CharacterClass.BountyHunter:
                case CharacterClass.Trooper:
                case CharacterClass.Agent:
                case CharacterClass.Smuggler:
                    slotList.Add(SlotId.EquipHumanRangedPrimary);
                    break;
                case CharacterClass.Inquisitor:
                case CharacterClass.Consular:
                case CharacterClass.Warrior:
                case CharacterClass.Knight:
                    slotList.Add(SlotId.EquipHumanMainHand);
                    break;
            }

            switch (advanced)
            {
                case AdvancedClass.Mercenary:
                case AdvancedClass.Gunslinger:
                    slotList.Add(SlotId.EquipHumanRangedSecondary);
                    doubleWeaps = true;
                    break;
                case AdvancedClass.Powertech:
                case AdvancedClass.Vanguard:
                case AdvancedClass.Commando:
                    slotList.Add(SlotId.EquipHumanShield);
                    break;
                case AdvancedClass.Guardian:
                case AdvancedClass.Juggernaut:
                case AdvancedClass.Sage:
                case AdvancedClass.Sorcerer:
                case AdvancedClass.Assassin:
                case AdvancedClass.Shadow:
                    slotList.Add(SlotId.EquipHumanFocus);
                    break;
                case AdvancedClass.Operative:
                case AdvancedClass.Sniper:
                    slotList.Add(SlotId.EquipHumanCustomMelee);
                    break;
                case AdvancedClass.Marauder:
                case AdvancedClass.Sentinel:
                    slotList.Add(SlotId.EquipHumanOffHand);
                    doubleWeaps = true;
                    break;
                case AdvancedClass.Scoundrel:
                    slotList.Add(SlotId.EquipHumanCustomRanged);
                    break;
            }

            slotList.Add(SlotId.EquipHumanEar);
            slotList.Add(SlotId.EquipHumanImplant);
            slotList.Add(SlotId.EquipHumanRelic);
            slotList.Add(SlotId.EquipHumanWrist);
            slotList.Add(SlotId.EquipHumanFace);
            slotList.Add(SlotId.EquipHumanChest);
            slotList.Add(SlotId.EquipHumanGlove);
            slotList.Add(SlotId.EquipHumanBelt);
            slotList.Add(SlotId.EquipHumanLeg);
            slotList.Add(SlotId.EquipHumanFoot);
        }
        #endregion

        #region Weight Calculator
        private float calculateWeight(TorItem item)
        {
            Dictionary<ModStat, float> itemStats = item.GetItemStats(incEnhancements);
            float total = 0;
            float weight = 0;
            //Loop through each stat in the item
            foreach (KeyValuePair<ModStat, float> stat in itemStats)
            {
                //If we have that stat weighted, calculate its subtotal and add to total. Otherwise, skip it.
                if (StatWeights.TryGetValue(stat.Key, out weight))
                {
                    //Multiply item's stat value by the weight
                    total += stat.Value * weight;
                    Log(item.Name + " - " + stat.Key + ": " + stat.Value + " Weight: " + stat.Value * weight);
                }
            }

            return total;
        }
        #endregion

    }
}
